<!DOCTYPE html>
<html lang="en">

<!-- Head tag -->
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="google-site-verification" content="xBT4GhYoi5qRD5tr338pgPM5OWHHIDR6mNg1a3euekI" />
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="keyword"  content="">
    <link rel="shortcut icon" href="/img/favicon.ico">

    <title>
        
          你也能写个 Shadowsocks - 浩麟的博客
        
    </title>

    <link rel="canonical" href="https://wuhaolin.cn/2017/11/03/你也能写个 Shadowsocks/">

    <!-- Bootstrap Core CSS -->
    
<link rel="stylesheet" href="/css/bootstrap.min.css">


    <!-- Custom CSS -->
    
<link rel="stylesheet" href="/css/hux-blog.min.css">


    <!-- Pygments Highlight CSS -->
    
<link rel="stylesheet" href="/css/highlight.css">


    <!-- Custom Fonts -->
    <!-- <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet" type="text/css"> -->
    <!-- Hux change font-awesome CDN to qiniu -->
    <link href="https://cdn.staticfile.org/font-awesome/4.5.0/css/font-awesome.min.css" rel="stylesheet" type="text/css">


    <!-- Hux Delete, sad but pending in China
    <link href='http://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic' rel='stylesheet' type='text/css'>
    <link href='http://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800' rel='stylesheet' type='text/
    css'>
    -->


    <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
        <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
        <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
    <![endif]-->

    <!-- ga & ba script hoook -->
    <script></script>
<meta name="generator" content="Hexo 4.1.1"></head>


<!-- hack iOS CSS :active style -->
<body ontouchstart="">

<!-- Navigation -->
<nav class="navbar navbar-default navbar-custom navbar-fixed-top">
    <div class="container-fluid">
        <!-- Brand and toggle get grouped for better mobile display -->
        <div class="navbar-header page-scroll">
            <button type="button" class="navbar-toggle">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="/">浩麟的博客</a>
        </div>

        <!-- Collect the nav links, forms, and other content for toggling -->
        <!-- Known Issue, found by Hux:
            <nav>'s height woule be hold on by its content.
            so, when navbar scale out, the <nav> will cover tags.
            also mask any touch event of tags, unfortunately.
        -->
        <div id="huxblog_navbar">
            <div class="navbar-collapse">
                <ul class="nav navbar-nav navbar-right">
                    <li>
                        <a href="/">Home</a>
                    </li>
                    <li>
                        <a href="https://github.com/gwuhaolin/blog/" target="_blank">订阅</a>
                    </li>

                    

                        
                    

                        
                        <li>
                            <a href="/about/">关于</a>
                        </li>
                        
                    

                        
                        <li>
                            <a href="/archives/">归档</a>
                        </li>
                        
                    

                        
                        <li>
                            <a href="/tags/">标签</a>
                        </li>
                        
                    

                </ul>
            </div>
        </div>
        <!-- /.navbar-collapse -->
    </div>
    <!-- /.container -->
</nav>
<script>
    // Drop Bootstarp low-performance Navbar
    // Use customize navbar with high-quality material design animation
    // in high-perf jank-free CSS3 implementation
    var $body   = document.body;
    var $toggle = document.querySelector('.navbar-toggle');
    var $navbar = document.querySelector('#huxblog_navbar');
    var $collapse = document.querySelector('.navbar-collapse');

    $toggle.addEventListener('click', handleMagic)
    function handleMagic(e){
        if ($navbar.className.indexOf('in') > 0) {
        // CLOSE
            $navbar.className = " ";
            // wait until animation end.
            setTimeout(function(){
                // prevent frequently toggle
                if($navbar.className.indexOf('in') < 0) {
                    $collapse.style.height = "0px"
                }
            },400)
        }else{
        // OPEN
            $collapse.style.height = "auto"
            $navbar.className += " in";
        }
    }
</script>


<!-- Main Content -->

<!-- Image to hack wechat -->
<!-- <img src="https://wuhaolin.cn/img/icon_wechat.png" width="0" height="0"> -->
<!-- <img src="{{ site.baseurl }}/{% if page.header-img %}{{ page.header-img }}{% else %}{{ site.header-img }}{% endif %}" width="0" height="0"> -->

<!-- Post Header -->
<style type="text/css">
    header.intro-header{
        background-image: url('/img/bg.jpg')
    }
</style>
<header class="intro-header" >
    <div class="container">
        <div class="row">
            <div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
                <div class="post-heading">
                    <div class="tags">
                        
                          <a class="tag" href="/tags/#网络" title="网络">网络</a>
                        
                    </div>
                    <h1>你也能写个 Shadowsocks</h1>
                    <h2 class="subheading"></h2>
                    <span class="meta">
                        Posted by 吴浩麟 on
                        2017-11-03
                    </span>
                </div>
            </div>
        </div>
    </div>
</header>

<!-- Post Content -->
<article>
    <div class="container">
        <div class="row">
    <!-- Post Container -->
            <div class="
                col-lg-8 col-lg-offset-2
                col-md-10 col-md-offset-1
                post-container">

                <p>本文将教你从0写一个<a href="https://github.com/shadowsocks/shadowsocks-go" target="_blank" rel="noopener">Shadowsocks</a>，无需任何基础，读完本文你就能完成一个轻量级、高性能的 Shadowsocks 代替品。</p>
<p>我们暂且把最终完成的项目叫做 Lightsocks，如果你很急切地想看到结果，可以先体验本文最终完成的项目 <a href="https://github.com/gwuhaolin/lightsocks" target="_blank" rel="noopener">Lightsocks</a> ，也可以下载阅读源码。</p>
<h2 id="认识-Shadowsocks"><a href="#认识-Shadowsocks" class="headerlink" title="认识 Shadowsocks"></a>认识 Shadowsocks</h2><p>Shadowsocks 是一个能骗过防火墙的网络代理工具。它把要传输的原数据经过加密后再传输，网络中的防火墙由于得不出要传输的原内容是什么而只好放行，于是就完成了防火墙穿透，也即是所谓的“翻墙”。</p>
<p>在自由的网络环境下，在本机上访问服务时是直接和远程服务建立连接传输数据，流程如图：<br><img src="https://user-images.githubusercontent.com/5773264/32371367-8c231024-c05e-11e7-87a8-f977577a6b89.png" alt="自由网络环境下的传输流程"></p>
<p>但在受限的网络环境下会有防火墙，本机电脑和远程服务之间传输的数据都必须通过防火墙的检查，流程如图：<br><img src="https://user-images.githubusercontent.com/5773264/32371437-d8cb8852-c05e-11e7-9872-a6708bbe65ba.png" alt="受限网络环境下的传输流程"><br>如果防火墙发现你在传输受限的内容，就把拦截本次传输，就会导致在本机无法访问远程服务。</p>
<p>而 Shadowsocks 所做的就是把传输的数据加密，防火墙得到的数据是加密后的数据，防火墙不知道传输的原内容是什么，于是防火墙就放行本次请求，于是在本机就访问到了远程服务，流程如图：<br><img src="https://user-images.githubusercontent.com/5773264/32371932-cdc55044-c060-11e7-9b0e-c1a7fec2b428.png" alt="shadowsocks下的传输流程"></p>
<p>也就是说使用 Shadowsocks 的前提是：</p>
<ul>
<li>一台在防火墙之外的服务器；</li>
<li>在本机需要安装 Shadowsocks 本地端，用于加密传输数据；</li>
<li>服务器需要安装 Shadowsocks 服务端，用于解密加密后的传输数据，解密出原数据后发送到目标服务器。</li>
</ul>
<h2 id="Shadowsocks-原理"><a href="#Shadowsocks-原理" class="headerlink" title="Shadowsocks 原理"></a>Shadowsocks 原理</h2><p>Shadowsocks 由两部分组成，运行在本地的 ss-local 和运行在防火墙之外服务器上的 ss-server，下面来分别详细介绍它们的职责（以下对 Shadowsocks 原理的解析只是我的大概估计，可能会有细微的差别）。</p>
<h4 id="ss-local"><a href="#ss-local" class="headerlink" title="ss-local"></a>ss-local</h4><p>ss-local 的职责是在本机启动和监听着一个服务，本地软件的网络请求都先发送到 ss-local，ss-local 收到来自本地软件的网络请求后，把要传输的原数据根据用户配置的加密方法和密码进行加密，再转发到墙外的服务器去。</p>
<h4 id="ss-server"><a href="#ss-server" class="headerlink" title="ss-server"></a>ss-server</h4><p>ss-server 的职责是在墙外服务器启动和监听一个服务，该服务监听来自本机的 ss-local 的请求。在收到来自 ss-local 转发过来的数据时，会先根据用户配置的加密方法和密码对数据进行对称解密，以获得加密后的数据的原内容。同时还会解 SOCKS5 协议，读出本次请求真正的目标服务地址(例如 Google 服务器地址)，再把解密后得到的原数据转发到真正的目标服务。</p>
<p>当真正的目标服务返回了数据时，ss-server 端会把返回的数据加密后转发给对应的 ss-local 端，ss-local 端收到数据再解密后，转发给本机的软件。这是一个对称相反的过程。</p>
<p>由于 ss-local 和 ss-server 端都需要用对称加密算法对数据进行加密和解密，因此这两端的加密方法和密码必须配置为一样。Shadowsocks 提供了一系列标准可靠的对称算法可供用户选择，例如 rc4、aes、des、chacha20 等等。Shadowsocks 对数据加密后再传输的目的是为了混淆原数据，让途中的防火墙无法得出传输的原数据。但其实用这些安全性高计算量大的对称加密算法去实现混淆有点“杀鸡用牛刀”。</p>
<h2 id="SOCKS5-协议介绍"><a href="#SOCKS5-协议介绍" class="headerlink" title="SOCKS5 协议介绍"></a>SOCKS5 协议介绍</h2><p>Shadowsocks 的数据传输是建立在 SOCKS5 协议之上的，SOCKS5 是 TCP/IP 层面的网络代理协议。<br>ss-server 端解密出来的数据就是采用 SOCKS5 协议封装的，通过 SOCKS5 协议 ss-server 端能读出本机软件想访问的服务的真正地址以及要传输的原数据，下面来详细介绍  SOCKS5 协议的通信细节。</p>
<h4 id="建立连接"><a href="#建立连接" class="headerlink" title="建立连接"></a>建立连接</h4><p>客户端向服务端连接连接，客户端发送的数据包如下：</p>
<table>
<thead>
<tr>
<th>VER</th>
<th>NMETHODS</th>
<th>METHODS</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>1</td>
<td>1</td>
</tr>
</tbody>
</table>
<p>其中各个字段的含义如下：<br>-<code>VER</code>：代表 SOCKS 的版本，SOCKS5 默认为<code>0x05</code>，其固定长度为1个字节；<br>-<code>NMETHODS</code>：表示第三个字段METHODS的长度，它的长度也是1个字节；<br>-<code>METHODS</code>：表示客户端支持的验证方式，可以有多种，他的长度是1-255个字节。</p>
<p>目前支持的验证方式共有：</p>
<ul>
<li><code>0x00</code>：NO AUTHENTICATION REQUIRED（不需要验证）</li>
<li><code>0x01</code>：GSSAPI</li>
<li><code>0x02</code>：USERNAME/PASSWORD（用户名密码）</li>
<li><code>0x03</code>: to X’7F’ IANA ASSIGNED</li>
<li><code>0x80</code>: to X’FE’ RESERVED FOR PRIVATE METHODS</li>
<li><code>0xFF</code>: NO ACCEPTABLE METHODS（都不支持，没法连接了）</li>
</ul>
<h4 id="响应连接"><a href="#响应连接" class="headerlink" title="响应连接"></a>响应连接</h4><p>服务端收到客户端的验证信息之后，就要回应客户端，服务端需要客户端提供哪种验证方式的信息。服务端回应的包格式如下：</p>
<table>
<thead>
<tr>
<th>VER</th>
<th>METHOD</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>1</td>
</tr>
</tbody>
</table>
<p>其中各个字段的含义如下：</p>
<ul>
<li><code>VER</code>：代表 SOCKS 的版本，SOCKS5 默认为<code>0x05</code>，其固定长度为1个字节；</li>
<li><code>METHOD</code>：代表服务端需要客户端按此验证方式提供的验证信息，其值长度为1个字节，可为上面六种验证方式之一。</li>
</ul>
<p>举例说明，比如服务端不需要验证的话，可以这么回应客户端：</p>
<table>
<thead>
<tr>
<th>VER</th>
<th>METHOD</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>0x05</code></td>
<td><code>0x00</code></td>
</tr>
</tbody>
</table>
<h4 id="和目标服务建立连接"><a href="#和目标服务建立连接" class="headerlink" title="和目标服务建立连接"></a>和目标服务建立连接</h4><p>客户端发起的连接由服务端验证通过后，客户端下一步应该告诉真正目标服务的地址给服务器，服务器得到地址后再去请求真正的目标服务。也就是说客户端需要把 Google 服务的地址<code>google.com:80</code>告诉服务端，服务端再去请求<code>google.com:80</code>。<br>目标服务地址的格式为 (IP或域名)+端口，客户端需要发送的包格式如下：</p>
<table>
<thead>
<tr>
<th>VER</th>
<th>CMD</th>
<th>RSV</th>
<th>ATYP</th>
<th>DST.ADDR</th>
<th>DST.PORT</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>1</td>
<td><code>0x00</code></td>
<td>1</td>
<td>Variable</td>
<td>2</td>
</tr>
</tbody>
</table>
<p>各个字段的含义如下：</p>
<ul>
<li><code>VER</code>：代表 SOCKS 协议的版本，SOCKS 默认为0x05，其值长度为1个字节；</li>
<li><code>CMD</code>：代表客户端请求的类型，值长度也是1个字节，有三种类型；<ul>
<li><code>CONNECT</code>： <code>0x01</code>；</li>
<li><code>BIND</code>： <code>0x02</code>；</li>
<li><code>UDP</code>： ASSOCIATE <code>0x03</code>；</li>
</ul>
</li>
<li><code>RSV</code>：保留字，值长度为1个字节；</li>
<li><code>ATYP</code>：代表请求的远程服务器地址类型，值长度1个字节，有三种类型；<ul>
<li><code>IPV4</code>： address: <code>0x01</code>；</li>
<li><code>DOMAINNAME</code>: <code>0x03</code>；</li>
<li><code>IPV6</code>： address: <code>0x04</code>；</li>
</ul>
</li>
<li><code>DST.ADDR</code>：代表远程服务器的地址，根据 <code>ATYP</code> 进行解析，值长度不定；</li>
<li><code>DST.PORT</code>：代表远程服务器的端口，要访问哪个端口的意思，值长度2个字节。</li>
</ul>
<p>服务端在得到来自客户端告诉的目标服务地址后，便和目标服务进行连接，不管连接成功与否，服务器都应该把连接的结果告诉客户端。在连接成功的情况下，服务端返回的包格式如下：</p>
<table>
<thead>
<tr>
<th>VER</th>
<th>REP</th>
<th>RSV</th>
<th>ATYP</th>
<th>BND.ADDR</th>
<th>BND.PORT</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>1</td>
<td><code>0x00</code></td>
<td>1</td>
<td>Variable</td>
<td>2</td>
</tr>
</tbody>
</table>
<p>各个字段的含义如下：</p>
<ul>
<li><code>VER</code>：代表 SOCKS 协议的版本，SOCKS 默认为0x05，其值长度为1个字节；</li>
<li>REP代表响应状态码，值长度也是1个字节，有以下几种类型<ul>
<li><code>0x00</code> succeeded</li>
<li><code>0x01</code> general SOCKS server failure</li>
<li><code>0x02</code> connection not allowed by ruleset</li>
<li><code>0x03</code> Network unreachable</li>
<li><code>0x04</code> Host unreachable</li>
<li><code>0x05</code> Connection refused</li>
<li><code>0x06</code> TTL expired</li>
<li><code>0x07</code> Command not supported</li>
<li><code>0x08</code> Address type not supported</li>
<li><code>0x09</code> to <code>0xFF</code> unassigned</li>
</ul>
</li>
<li><code>RSV</code>：保留字，值长度为1个字节</li>
<li><code>ATYP</code>：代表请求的远程服务器地址类型，值长度1个字节，有三种类型<ul>
<li>IP V4 address： <code>0x01</code></li>
<li>DOMAINNAME： <code>0x03</code></li>
<li>IP V6 address： <code>0x04</code></li>
</ul>
</li>
<li><code>BND.ADDR</code>：表示绑定地址，值长度不定。</li>
<li><code>BND.PORT</code>： 表示绑定端口，值长度2个字节</li>
</ul>
<h4 id="数据转发"><a href="#数据转发" class="headerlink" title="数据转发"></a>数据转发</h4><p>客户端在收到来自服务器成功的响应后，就会开始发送数据了，服务端在收到来自客户端的数据后，会转发到目标服务。</p>
<h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><p>SOCKS5 协议的目的其实就是为了把来自原本应该在本机直接请求目标服务的流程，放到了服务端去代理客户端访问。<br>其运行流程总结如下：</p>
<ol>
<li>本机和代理服务端协商和建立连接；</li>
<li>本机告诉代理服务端目标服务的地址；</li>
<li>代理服务端去连接目标服务，成功后告诉本机；</li>
<li>本机开始发送原本应发送到目标服务的数据给代理服务端，由代理服务端完成数据转发。</li>
</ol>
<p>以上内容来自 <a href="http://www.ietf.org/rfc/rfc1928.txt" target="_blank" rel="noopener">SOCKS5 协议规范 rfc1928</a>。</p>
<h2 id="Lightsocks-实现"><a href="#Lightsocks-实现" class="headerlink" title="Lightsocks 实现"></a><a href="https://github.com/gwuhaolin/lightsocks" target="_blank" rel="noopener">Lightsocks</a> 实现</h2><p>要实现 Lightsocks 需要实现两部分：运行在本地的 lightsocks-local，和运行在墙外代理服务器上 lightsocks-server。<br>下面来分别教你如果使用 Golang 来实现它们，采用 Golang 语言的原因在于：性能好、跨平台、适合高并发、学习门槛低。对Golang感兴趣？请看<a href="http://go.wuhaolin.cn/" target="_blank" rel="noopener">Golang 中文学习资料汇总</a></p>
<h4 id="实现数据混淆"><a href="#实现数据混淆" class="headerlink" title="实现数据混淆"></a>实现数据混淆</h4><p>在 Shadowsocks 中是采用的标准的对称加密算法去实现数据混淆的，对称算法在加密和解密过程中需要大量计算。<br>为了简单起见，Lightsocks 将采用最简单高效的方法去实现数据混淆，具体原理如下。</p>
<p>这个数据混淆算法和对称加密很相似，两端都需要有同样的密钥。<br>这个密钥有如下要求：</p>
<ul>
<li>由256个 byte 组成，也就是一个数组，在 Golang 中类型表示为 <code>[256]byte</code>；</li>
<li>这个数组必须由 0～255 这256个数字组成，一个都不能差；</li>
<li>这个数组中第<code>I</code>个的值不能等于<code>I</code>；</li>
</ul>
<p>例如以下为一个合法的密钥(上为索引，下为值)：</p>
<table>
<thead>
<tr>
<th>0</th>
<th>1</th>
<th>2</th>
<th>3</th>
<th>4</th>
<th>5</th>
<th>6</th>
<th>7</th>
<th>8</th>
<th>9</th>
<th>10</th>
<th>11</th>
<th>12</th>
<th>13</th>
<th>14</th>
<th>15</th>
<th>16</th>
<th>17</th>
<th>18</th>
<th>19</th>
<th>20</th>
<th>21</th>
<th>22</th>
<th>23</th>
<th>24</th>
<th>25</th>
<th>26</th>
<th>27</th>
<th>28</th>
<th>29</th>
<th>30</th>
<th>31</th>
<th>32</th>
<th>33</th>
<th>34</th>
<th>35</th>
<th>36</th>
<th>37</th>
<th>38</th>
<th>39</th>
<th>40</th>
<th>41</th>
<th>42</th>
<th>43</th>
<th>44</th>
<th>45</th>
<th>46</th>
<th>47</th>
<th>48</th>
<th>49</th>
<th>50</th>
<th>51</th>
<th>52</th>
<th>53</th>
<th>54</th>
<th>55</th>
<th>56</th>
<th>57</th>
<th>58</th>
<th>59</th>
<th>60</th>
<th>61</th>
<th>62</th>
<th>63</th>
<th>64</th>
<th>65</th>
<th>66</th>
<th>67</th>
<th>68</th>
<th>69</th>
<th>70</th>
<th>71</th>
<th>72</th>
<th>73</th>
<th>74</th>
<th>75</th>
<th>76</th>
<th>77</th>
<th>78</th>
<th>79</th>
<th>80</th>
<th>81</th>
<th>82</th>
<th>83</th>
<th>84</th>
<th>85</th>
<th>86</th>
<th>87</th>
<th>88</th>
<th>89</th>
<th>90</th>
<th>91</th>
<th>92</th>
<th>93</th>
<th>94</th>
<th>95</th>
<th>96</th>
<th>97</th>
<th>98</th>
<th>99</th>
<th>100</th>
<th>101</th>
<th>102</th>
<th>103</th>
<th>104</th>
<th>105</th>
<th>106</th>
<th>107</th>
<th>108</th>
<th>109</th>
<th>110</th>
<th>111</th>
<th>112</th>
<th>113</th>
<th>114</th>
<th>115</th>
<th>116</th>
<th>117</th>
<th>118</th>
<th>119</th>
<th>120</th>
<th>121</th>
<th>122</th>
<th>123</th>
<th>124</th>
<th>125</th>
<th>126</th>
<th>127</th>
<th>128</th>
<th>129</th>
<th>130</th>
<th>131</th>
<th>132</th>
<th>133</th>
<th>134</th>
<th>135</th>
<th>136</th>
<th>137</th>
<th>138</th>
<th>139</th>
<th>140</th>
<th>141</th>
<th>142</th>
<th>143</th>
<th>144</th>
<th>145</th>
<th>146</th>
<th>147</th>
<th>148</th>
<th>149</th>
<th>150</th>
<th>151</th>
<th>152</th>
<th>153</th>
<th>154</th>
<th>155</th>
<th>156</th>
<th>157</th>
<th>158</th>
<th>159</th>
<th>160</th>
<th>161</th>
<th>162</th>
<th>163</th>
<th>164</th>
<th>165</th>
<th>166</th>
<th>167</th>
<th>168</th>
<th>169</th>
<th>170</th>
<th>171</th>
<th>172</th>
<th>173</th>
<th>174</th>
<th>175</th>
<th>176</th>
<th>177</th>
<th>178</th>
<th>179</th>
<th>180</th>
<th>181</th>
<th>182</th>
<th>183</th>
<th>184</th>
<th>185</th>
<th>186</th>
<th>187</th>
<th>188</th>
<th>189</th>
<th>190</th>
<th>191</th>
<th>192</th>
<th>193</th>
<th>194</th>
<th>195</th>
<th>196</th>
<th>197</th>
<th>198</th>
<th>199</th>
<th>200</th>
<th>201</th>
<th>202</th>
<th>203</th>
<th>204</th>
<th>205</th>
<th>206</th>
<th>207</th>
<th>208</th>
<th>209</th>
<th>210</th>
<th>211</th>
<th>212</th>
<th>213</th>
<th>214</th>
<th>215</th>
<th>216</th>
<th>217</th>
<th>218</th>
<th>219</th>
<th>220</th>
<th>221</th>
<th>222</th>
<th>223</th>
<th>224</th>
<th>225</th>
<th>226</th>
<th>227</th>
<th>228</th>
<th>229</th>
<th>230</th>
<th>231</th>
<th>232</th>
<th>233</th>
<th>234</th>
<th>235</th>
<th>236</th>
<th>237</th>
<th>238</th>
<th>239</th>
<th>240</th>
<th>241</th>
<th>242</th>
<th>243</th>
<th>244</th>
<th>245</th>
<th>246</th>
<th>247</th>
<th>248</th>
<th>249</th>
<th>250</th>
<th>251</th>
<th>252</th>
<th>253</th>
<th>254</th>
<th>255</th>
</tr>
</thead>
<tbody>
<tr>
<td>186</td>
<td>118</td>
<td>82</td>
<td>201</td>
<td>235</td>
<td>236</td>
<td>180</td>
<td>66</td>
<td>228</td>
<td>96</td>
<td>43</td>
<td>90</td>
<td>203</td>
<td>200</td>
<td>34</td>
<td>104</td>
<td>41</td>
<td>222</td>
<td>165</td>
<td>74</td>
<td>240</td>
<td>20</td>
<td>244</td>
<td>67</td>
<td>114</td>
<td>191</td>
<td>220</td>
<td>147</td>
<td>196</td>
<td>183</td>
<td>229</td>
<td>123</td>
<td>208</td>
<td>19</td>
<td>127</td>
<td>187</td>
<td>84</td>
<td>148</td>
<td>56</td>
<td>170</td>
<td>133</td>
<td>160</td>
<td>202</td>
<td>21</td>
<td>53</td>
<td>78</td>
<td>59</td>
<td>64</td>
<td>120</td>
<td>27</td>
<td>167</td>
<td>175</td>
<td>39</td>
<td>10</td>
<td>4</td>
<td>132</td>
<td>89</td>
<td>230</td>
<td>152</td>
<td>73</td>
<td>221</td>
<td>88</td>
<td>141</td>
<td>158</td>
<td>251</td>
<td>79</td>
<td>225</td>
<td>87</td>
<td>14</td>
<td>23</td>
<td>68</td>
<td>250</td>
<td>199</td>
<td>168</td>
<td>218</td>
<td>60</td>
<td>40</td>
<td>169</td>
<td>75</td>
<td>86</td>
<td>153</td>
<td>134</td>
<td>83</td>
<td>49</td>
<td>128</td>
<td>231</td>
<td>217</td>
<td>239</td>
<td>226</td>
<td>177</td>
<td>57</td>
<td>24</td>
<td>234</td>
<td>63</td>
<td>7</td>
<td>112</td>
<td>166</td>
<td>211</td>
<td>254</td>
<td>179</td>
<td>157</td>
<td>215</td>
<td>227</td>
<td>224</td>
<td>233</td>
<td>81</td>
<td>172</td>
<td>26</td>
<td>122</td>
<td>219</td>
<td>48</td>
<td>151</td>
<td>232</td>
<td>50</td>
<td>108</td>
<td>44</td>
<td>0</td>
<td>192</td>
<td>65</td>
<td>76</td>
<td>109</td>
<td>252</td>
<td>248</td>
<td>47</td>
<td>154</td>
<td>33</td>
<td>209</td>
<td>115</td>
<td>31</td>
<td>15</td>
<td>45</td>
<td>206</td>
<td>247</td>
<td>124</td>
<td>77</td>
<td>8</td>
<td>182</td>
<td>144</td>
<td>1</td>
<td>72</td>
<td>131</td>
<td>52</td>
<td>245</td>
<td>198</td>
<td>238</td>
<td>5</td>
<td>188</td>
<td>116</td>
<td>55</td>
<td>216</td>
<td>155</td>
<td>2</td>
<td>178</td>
<td>189</td>
<td>162</td>
<td>136</td>
<td>243</td>
<td>184</td>
<td>58</td>
<td>69</td>
<td>70</td>
<td>99</td>
<td>36</td>
<td>25</td>
<td>35</td>
<td>174</td>
<td>195</td>
<td>18</td>
<td>205</td>
<td>30</td>
<td>190</td>
<td>142</td>
<td>210</td>
<td>113</td>
<td>145</td>
<td>101</td>
<td>97</td>
<td>161</td>
<td>100</td>
<td>91</td>
<td>242</td>
<td>138</td>
<td>93</td>
<td>171</td>
<td>98</td>
<td>237</td>
<td>212</td>
<td>255</td>
<td>80</td>
<td>102</td>
<td>119</td>
<td>204</td>
<td>107</td>
<td>105</td>
<td>111</td>
<td>11</td>
<td>29</td>
<td>146</td>
<td>129</td>
<td>117</td>
<td>135</td>
<td>176</td>
<td>163</td>
<td>207</td>
<td>103</td>
<td>22</td>
<td>246</td>
<td>125</td>
<td>150</td>
<td>106</td>
<td>126</td>
<td>197</td>
<td>249</td>
<td>62</td>
<td>51</td>
<td>193</td>
<td>32</td>
<td>3</td>
<td>110</td>
<td>46</td>
<td>85</td>
<td>71</td>
<td>159</td>
<td>139</td>
<td>12</td>
<td>164</td>
<td>95</td>
<td>121</td>
<td>140</td>
<td>241</td>
<td>253</td>
<td>130</td>
<td>173</td>
<td>213</td>
<td>54</td>
<td>143</td>
<td>16</td>
<td>94</td>
<td>9</td>
<td>61</td>
<td>156</td>
<td>214</td>
<td>28</td>
<td>17</td>
<td>37</td>
<td>42</td>
<td>181</td>
<td>149</td>
<td>185</td>
<td>223</td>
<td>92</td>
<td>38</td>
<td>13</td>
<td>194</td>
<td>6</td>
<td>137</td>
</tr>
</tbody>
</table>
<p>如果原数据为 <code>[5,0,1,2,3]</code>，则采用以上密钥加密后变成 <code>[236,186,118,82,201]</code>。<br>如果加密后的数据为 <code>[186,118,82,201,235]</code>，则采用以上密钥解密得到的原数据为 <code>[0,1,2,3,4]</code></p>
<p>聪明的你肯定看懂了其中的规律：把1～255 这256个数字确定一种一对一的映射关系，加密是从一个数字得到对应的一个数字，而解密则是反向的过程，而这个密钥的作用正是描述这个映射关系。<br>这其实就是中学学的<strong>反函数</strong>。</p>
<p>为什么要这样设计数据混淆算法呢？在数据传输时，数据是以 byte 为最小单位流式传输的。一个 byte 的取值只可能是 0～255。该混淆算法可以直接对一个个 byte 进行加解密，而无需像标准的对称算法那样只能对一大块数据进行加密。<br>再加上本算法的加解密 N byte 数据的算法复杂度为 N（直接通过数组索引访问），非常适合流式加密。</p>
<p>以上加密算法的安全性怎么样呢？符合以上要求的密钥匙有多少种组合呢？我们来算算：<br>这其实就是初中学的排列组合中的排列问题，形象点其实就是，把 0～255 个不同编号的人安排到 0～255 个不同编号的坑去，并且不能有编号一样的情况，有多少种排法。<br>也就是 <code>A(255,255)=255*254*253*...*1=255!</code>，但其中有一半为有重复的情况，<br>最终结果为 <code>255!/2</code>，<br>其值大概为 <code>10^500</code> 这个数量级。</p>
<p>以上加密算法虽然破绽很多，但足以实现高效的数据混淆，骗过防火墙。</p>
<p>目前采用对称加密算法实现数据混淆的 Shadowsocks 已经能被一些防火墙通过机器学习算法通过特征分析识别出传输的原内容适合合法，而 Lightsocks 的这套混淆算法目前还不能被轻易的识别出来。</p>
<p>随机产生一个以上密钥匙的<a href="https://github.com/gwuhaolin/lightsocks/blob/master/core/password.go" target="_blank" rel="noopener">代码如下</a>：<br><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> core</span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">	<span class="string">"math/rand"</span></span><br><span class="line">	<span class="string">"time"</span></span><br><span class="line">)</span><br><span class="line"><span class="keyword">const</span> PasswordLength = <span class="number">256</span></span><br><span class="line"><span class="keyword">type</span> Password [PasswordLength]<span class="keyword">byte</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">init</span><span class="params">()</span></span> &#123;</span><br><span class="line">	<span class="comment">// 更新随机种子，防止生成一样的随机密码</span></span><br><span class="line">	rand.Seed(time.Now().Unix())</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 产生 256个byte随机组合的 密码</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">RandPassword</span><span class="params">()</span> *<span class="title">Password</span></span> &#123;</span><br><span class="line">	<span class="comment">// 随机生成一个由  0~255 组成的 byte 数组</span></span><br><span class="line">	intArr := rand.Perm(PasswordLength)</span><br><span class="line">	password := &amp;Password&#123;&#125;</span><br><span class="line">	<span class="keyword">for</span> i, v := <span class="keyword">range</span> intArr &#123;</span><br><span class="line">		password[i] = <span class="keyword">byte</span>(v)</span><br><span class="line">		<span class="keyword">if</span> i == v &#123;</span><br><span class="line">			<span class="comment">// 确保不会出现如何一个byte位出现重复</span></span><br><span class="line">			<span class="keyword">return</span> RandPassword()</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">return</span> password</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>对数据进行加密解密的<a href="https://github.com/gwuhaolin/lightsocks/blob/master/core/cipher.go" target="_blank" rel="noopener">代码如下</a>：<br><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> core</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Cipher <span class="keyword">struct</span> &#123;</span><br><span class="line">	<span class="comment">// 编码用的密码</span></span><br><span class="line">	encodePassword *Password</span><br><span class="line">	<span class="comment">// 解码用的密码</span></span><br><span class="line">	decodePassword *Password</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 加密原数据</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(cipher *Cipher)</span> <span class="title">encode</span><span class="params">(bs []<span class="keyword">byte</span>)</span></span> &#123;</span><br><span class="line">	<span class="keyword">for</span> i, v := <span class="keyword">range</span> bs &#123;</span><br><span class="line">		bs[i] = cipher.encodePassword[v]</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 解码加密后的数据到原数据</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(cipher *Cipher)</span> <span class="title">decode</span><span class="params">(bs []<span class="keyword">byte</span>)</span></span> &#123;</span><br><span class="line">	<span class="keyword">for</span> i, v := <span class="keyword">range</span> bs &#123;</span><br><span class="line">		bs[i] = cipher.decodePassword[v]</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 新建一个编码解码器</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewCipher</span><span class="params">(encodePassword *Password)</span> *<span class="title">Cipher</span></span> &#123;</span><br><span class="line">	decodePassword := &amp;Password&#123;&#125;</span><br><span class="line">	<span class="keyword">for</span> i, v := <span class="keyword">range</span> encodePassword &#123;</span><br><span class="line">		encodePassword[i] = v</span><br><span class="line">		decodePassword[v] = <span class="keyword">byte</span>(i)</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">return</span> &amp;Cipher&#123;</span><br><span class="line">		encodePassword: encodePassword,</span><br><span class="line">		decodePassword: decodePassword,</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>再使用以上的 Cipher 去封装一个加密传输的 SecureSocket，以方便直接加解密 TCP Socket 中的流式数据，<a href="https://github.com/gwuhaolin/lightsocks/blob/master/core/securesocket.go" target="_blank" rel="noopener">代码如下</a>：<br><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> core</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">	<span class="string">"errors"</span></span><br><span class="line">	<span class="string">"fmt"</span></span><br><span class="line">	<span class="string">"io"</span></span><br><span class="line">	<span class="string">"net"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> (</span><br><span class="line">	BufSize = <span class="number">1024</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 加密传输的 TCP Socket</span></span><br><span class="line"><span class="keyword">type</span> SecureSocket <span class="keyword">struct</span> &#123;</span><br><span class="line">	Cipher     *Cipher</span><br><span class="line">	ListenAddr *net.TCPAddr</span><br><span class="line">	RemoteAddr *net.TCPAddr</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 从输入流里读取加密过的数据，解密后把原数据放到bs里</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(secureSocket *SecureSocket)</span> <span class="title">DecodeRead</span><span class="params">(conn *net.TCPConn, bs []<span class="keyword">byte</span>)</span> <span class="params">(n <span class="keyword">int</span>, err error)</span></span> &#123;</span><br><span class="line">	n, err = conn.Read(bs)</span><br><span class="line">	<span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">		<span class="keyword">return</span></span><br><span class="line">	&#125;</span><br><span class="line">	secureSocket.Cipher.decode(bs[:n])</span><br><span class="line">	<span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 把放在bs里的数据加密后立即全部写入输出流</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(secureSocket *SecureSocket)</span> <span class="title">EncodeWrite</span><span class="params">(conn *net.TCPConn, bs []<span class="keyword">byte</span>)</span> <span class="params">(<span class="keyword">int</span>, error)</span></span> &#123;</span><br><span class="line">	secureSocket.Cipher.encode(bs)</span><br><span class="line">	<span class="keyword">return</span> conn.Write(bs)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 从src中源源不断的读取原数据加密后写入到dst，直到src中没有数据可以再读取</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(secureSocket *SecureSocket)</span> <span class="title">EncodeCopy</span><span class="params">(dst *net.TCPConn, src *net.TCPConn)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">	buf := <span class="built_in">make</span>([]<span class="keyword">byte</span>, BufSize)</span><br><span class="line">	<span class="keyword">for</span> &#123;</span><br><span class="line">		readCount, errRead := src.Read(buf)</span><br><span class="line">		<span class="keyword">if</span> errRead != <span class="literal">nil</span> &#123;</span><br><span class="line">			<span class="keyword">if</span> errRead != io.EOF &#123;</span><br><span class="line">				<span class="keyword">return</span> errRead</span><br><span class="line">			&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">				<span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">			&#125;</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">if</span> readCount &gt; <span class="number">0</span> &#123;</span><br><span class="line">			writeCount, errWrite := secureSocket.EncodeWrite(dst, buf[<span class="number">0</span>:readCount])</span><br><span class="line">			<span class="keyword">if</span> errWrite != <span class="literal">nil</span> &#123;</span><br><span class="line">				<span class="keyword">return</span> errWrite</span><br><span class="line">			&#125;</span><br><span class="line">			<span class="keyword">if</span> readCount != writeCount &#123;</span><br><span class="line">				<span class="keyword">return</span> io.ErrShortWrite</span><br><span class="line">			&#125;</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 从src中源源不断的读取加密后的数据解密后写入到dst，直到src中没有数据可以再读取</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(secureSocket *SecureSocket)</span> <span class="title">DecodeCopy</span><span class="params">(dst *net.TCPConn, src *net.TCPConn)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">	buf := <span class="built_in">make</span>([]<span class="keyword">byte</span>, BufSize)</span><br><span class="line">	<span class="keyword">for</span> &#123;</span><br><span class="line">		readCount, errRead := secureSocket.DecodeRead(src, buf)</span><br><span class="line">		<span class="keyword">if</span> errRead != <span class="literal">nil</span> &#123;</span><br><span class="line">			<span class="keyword">if</span> errRead != io.EOF &#123;</span><br><span class="line">				<span class="keyword">return</span> errRead</span><br><span class="line">			&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">				<span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">			&#125;</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">if</span> readCount &gt; <span class="number">0</span> &#123;</span><br><span class="line">			writeCount, errWrite := dst.Write(buf[<span class="number">0</span>:readCount])</span><br><span class="line">			<span class="keyword">if</span> errWrite != <span class="literal">nil</span> &#123;</span><br><span class="line">				<span class="keyword">return</span> errWrite</span><br><span class="line">			&#125;</span><br><span class="line">			<span class="keyword">if</span> readCount != writeCount &#123;</span><br><span class="line">				<span class="keyword">return</span> io.ErrShortWrite</span><br><span class="line">			&#125;</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 和远程的socket建立连接，他们之间的数据传输会加密</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(secureSocket *SecureSocket)</span> <span class="title">DialRemote</span><span class="params">()</span> <span class="params">(*net.TCPConn, error)</span></span> &#123;</span><br><span class="line">	remoteConn, err := net.DialTCP(<span class="string">"tcp"</span>, <span class="literal">nil</span>, secureSocket.RemoteAddr)</span><br><span class="line">	<span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">		<span class="keyword">return</span> <span class="literal">nil</span>, errors.New(fmt.Sprintf(<span class="string">"连接到远程服务器 %s 失败:%s"</span>, secureSocket.RemoteAddr, err))</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">return</span> remoteConn, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>这个 SecureSocket 用于 local 端和 server 端之间进行 TCP 通信，并且只使用 SecureSocket 通信时中间传输的数据会被加密，防火墙无法读到原数据。 </p>
<h4 id="实现-local-端"><a href="#实现-local-端" class="headerlink" title="实现 local 端"></a>实现 local 端</h4><p>运行在本机的 local 端的职责是把本机程序发送给它的数据经过加密后转发给墙外的代理服务器，总体工作流程如下：</p>
<ol>
<li>监听来自本机浏览器的代理请求；</li>
<li>转发前加密数据；</li>
<li>转发socket数据到墙外代理服务端；</li>
<li>把服务端返回的数据转发给用户的浏览器。</li>
</ol>
<p>实现以上功能的 local 端<a href="https://github.com/gwuhaolin/lightsocks/blob/master/local/local.go" target="_blank" rel="noopener">代码如下</a>：<br><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> local</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">	<span class="string">"github.com/gwuhaolin/lightsocks/core"</span></span><br><span class="line">	<span class="string">"log"</span></span><br><span class="line">	<span class="string">"net"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> LsLocal <span class="keyword">struct</span> &#123;</span><br><span class="line">	*core.SecureSocket</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 新建一个本地端</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">New</span><span class="params">(password *core.Password, listenAddr, remoteAddr *net.TCPAddr)</span> *<span class="title">LsLocal</span></span> &#123;</span><br><span class="line">	<span class="keyword">return</span> &amp;LsLocal&#123;</span><br><span class="line">		SecureSocket: &amp;core.SecureSocket&#123;</span><br><span class="line">			Cipher:     core.NewCipher(password),</span><br><span class="line">			ListenAddr: listenAddr,</span><br><span class="line">			RemoteAddr: remoteAddr,</span><br><span class="line">		&#125;,</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 本地端启动监听，接收来自本机浏览器的连接</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(local *LsLocal)</span> <span class="title">Listen</span><span class="params">(didListen <span class="keyword">func</span>(listenAddr net.Addr)</span>) <span class="title">error</span></span> &#123;</span><br><span class="line">	listener, err := net.ListenTCP(<span class="string">"tcp"</span>, local.ListenAddr)</span><br><span class="line">	<span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">		<span class="keyword">return</span> err</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="keyword">defer</span> listener.Close()</span><br><span class="line"></span><br><span class="line">	<span class="keyword">if</span> didListen != <span class="literal">nil</span> &#123;</span><br><span class="line">		didListen(listener.Addr())</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="keyword">for</span> &#123;</span><br><span class="line">		userConn, err := listener.AcceptTCP()</span><br><span class="line">		<span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">			log.Println(err)</span><br><span class="line">			<span class="keyword">continue</span></span><br><span class="line">		&#125;</span><br><span class="line">		<span class="comment">// userConn被关闭时直接清除所有数据 不管没有发送的数据</span></span><br><span class="line">		userConn.SetLinger(<span class="number">0</span>)</span><br><span class="line">		<span class="keyword">go</span> local.handleConn(userConn)</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(local *LsLocal)</span> <span class="title">handleConn</span><span class="params">(userConn *net.TCPConn)</span></span> &#123;</span><br><span class="line">	<span class="keyword">defer</span> userConn.Close()</span><br><span class="line"></span><br><span class="line">	proxyServer, err := local.DialRemote()</span><br><span class="line">	<span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">		log.Println(err)</span><br><span class="line">		<span class="keyword">return</span></span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">defer</span> proxyServer.Close()</span><br><span class="line">	<span class="comment">// Conn被关闭时直接清除所有数据 不管没有发送的数据</span></span><br><span class="line">	proxyServer.SetLinger(<span class="number">0</span>)</span><br><span class="line"></span><br><span class="line">	<span class="comment">// 进行转发</span></span><br><span class="line">	<span class="comment">// 从 proxyServer 读取数据发送到 localUser</span></span><br><span class="line">	<span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">		err := local.DecodeCopy(userConn, proxyServer)</span><br><span class="line">		<span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">			<span class="comment">// 在 copy 的过程中可能会存在网络超时等 error 被 return，只要有一个发生了错误就退出本次工作</span></span><br><span class="line">			userConn.Close()</span><br><span class="line">			proxyServer.Close()</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;()</span><br><span class="line">	<span class="comment">// 从 localUser 发送数据发送到 proxyServer，这里因为处在翻墙阶段出现网络错误的概率更大</span></span><br><span class="line">	local.EncodeCopy(proxyServer, userConn)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<h4 id="实现-server-端"><a href="#实现-server-端" class="headerlink" title="实现 server 端"></a>实现 server 端</h4><p>运行在墙外代理服务器的 server 端职责如下：</p>
<ol>
<li>监听来自本地代理客户端的请求；</li>
<li>解密本地代理客户端请求的数据，解析 SOCKS5 协议，连接用户浏览器真正想要连接的远程服务器；</li>
<li>转发用户浏览器真正想要连接的远程服务器返回的数据的加密后的内容到本地代理客户端。</li>
</ol>
<p>实现以上功能的<a href="https://github.com/gwuhaolin/lightsocks/blob/master/server/server.go" target="_blank" rel="noopener">代码如下</a>：<br><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> server</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">	<span class="string">"encoding/binary"</span></span><br><span class="line">	<span class="string">"github.com/gwuhaolin/lightsocks/core"</span></span><br><span class="line">	<span class="string">"log"</span></span><br><span class="line">	<span class="string">"net"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> LsServer <span class="keyword">struct</span> &#123;</span><br><span class="line">	*core.SecureSocket</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 新建一个服务端</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">New</span><span class="params">(password *core.Password, listenAddr *net.TCPAddr)</span> *<span class="title">LsServer</span></span> &#123;</span><br><span class="line">	<span class="keyword">return</span> &amp;LsServer&#123;</span><br><span class="line">		SecureSocket: &amp;core.SecureSocket&#123;</span><br><span class="line">			Cipher:     core.NewCipher(password),</span><br><span class="line">			ListenAddr: listenAddr,</span><br><span class="line">		&#125;,</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 运行服务端并且监听来自本地代理客户端的请求</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(lsServer *LsServer)</span> <span class="title">Listen</span><span class="params">(didListen <span class="keyword">func</span>(listenAddr net.Addr)</span>) <span class="title">error</span></span> &#123;</span><br><span class="line">	listener, err := net.ListenTCP(<span class="string">"tcp"</span>, lsServer.ListenAddr)</span><br><span class="line">	<span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">		<span class="keyword">return</span> err</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="keyword">defer</span> listener.Close()</span><br><span class="line"></span><br><span class="line">	<span class="keyword">if</span> didListen != <span class="literal">nil</span> &#123;</span><br><span class="line">		didListen(listener.Addr())</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="keyword">for</span> &#123;</span><br><span class="line">		localConn, err := listener.AcceptTCP()</span><br><span class="line">		<span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">			log.Println(err)</span><br><span class="line">			<span class="keyword">continue</span></span><br><span class="line">		&#125;</span><br><span class="line">		<span class="comment">// localConn被关闭时直接清除所有数据 不管没有发送的数据</span></span><br><span class="line">		localConn.SetLinger(<span class="number">0</span>)</span><br><span class="line">		<span class="keyword">go</span> lsServer.handleConn(localConn)</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 解 SOCKS5 协议</span></span><br><span class="line"><span class="comment">// https://www.ietf.org/rfc/rfc1928.txt</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(lsServer *LsServer)</span> <span class="title">handleConn</span><span class="params">(localConn *net.TCPConn)</span></span> &#123;</span><br><span class="line">	<span class="keyword">defer</span> localConn.Close()</span><br><span class="line">	buf := <span class="built_in">make</span>([]<span class="keyword">byte</span>, <span class="number">256</span>)</span><br><span class="line"></span><br><span class="line">	<span class="comment">/**</span></span><br><span class="line"><span class="comment">	   The localConn connects to the dstServer, and sends a ver</span></span><br><span class="line"><span class="comment">	   identifier/method selection message:</span></span><br><span class="line"><span class="comment">		          +----+----------+----------+</span></span><br><span class="line"><span class="comment">		          |VER | NMETHODS | METHODS  |</span></span><br><span class="line"><span class="comment">		          +----+----------+----------+</span></span><br><span class="line"><span class="comment">		          | 1  |    1     | 1 to 255 |</span></span><br><span class="line"><span class="comment">		          +----+----------+----------+</span></span><br><span class="line"><span class="comment">	   The VER field is set to X'05' for this ver of the protocol.  The</span></span><br><span class="line"><span class="comment">	   NMETHODS field contains the number of method identifier octets that</span></span><br><span class="line"><span class="comment">	   appear in the METHODS field.</span></span><br><span class="line"><span class="comment">	*/</span></span><br><span class="line">	<span class="comment">// 第一个字段VER代表Socks的版本，Socks5默认为0x05，其固定长度为1个字节</span></span><br><span class="line">	_, err := lsServer.DecodeRead(localConn, buf)</span><br><span class="line">	<span class="comment">// 只支持版本5</span></span><br><span class="line">	<span class="keyword">if</span> err != <span class="literal">nil</span> || buf[<span class="number">0</span>] != <span class="number">0x05</span> &#123;</span><br><span class="line">		<span class="keyword">return</span></span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="comment">/**</span></span><br><span class="line"><span class="comment">	   The dstServer selects from one of the methods given in METHODS, and</span></span><br><span class="line"><span class="comment">	   sends a METHOD selection message:</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">		          +----+--------+</span></span><br><span class="line"><span class="comment">		          |VER | METHOD |</span></span><br><span class="line"><span class="comment">		          +----+--------+</span></span><br><span class="line"><span class="comment">		          | 1  |   1    |</span></span><br><span class="line"><span class="comment">		          +----+--------+</span></span><br><span class="line"><span class="comment">	*/</span></span><br><span class="line">	<span class="comment">// 不需要验证，直接验证通过</span></span><br><span class="line">	lsServer.EncodeWrite(localConn, []<span class="keyword">byte</span>&#123;<span class="number">0x05</span>, <span class="number">0x00</span>&#125;)</span><br><span class="line"></span><br><span class="line">	<span class="comment">/**</span></span><br><span class="line"><span class="comment">		          +----+-----+-------+------+----------+----------+</span></span><br><span class="line"><span class="comment">		          |VER | CMD |  RSV  | ATYP | DST.ADDR | DST.PORT |</span></span><br><span class="line"><span class="comment">		          +----+-----+-------+------+----------+----------+</span></span><br><span class="line"><span class="comment">		          | 1  |  1  | X'00' |  1   | Variable |    2     |</span></span><br><span class="line"><span class="comment">		          +----+-----+-------+------+----------+----------+</span></span><br><span class="line"><span class="comment">	*/</span></span><br><span class="line"></span><br><span class="line">	<span class="comment">// 获取真正的远程服务的地址</span></span><br><span class="line">	n, err := lsServer.DecodeRead(localConn, buf)</span><br><span class="line">	<span class="comment">// n 最短的长度为7 情况为 ATYP=3 DST.ADDR占用1字节 值为0x0</span></span><br><span class="line">	<span class="keyword">if</span> err != <span class="literal">nil</span> || n &lt; <span class="number">7</span> &#123;</span><br><span class="line">		<span class="keyword">return</span></span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="comment">// CMD代表客户端请求的类型，值长度也是1个字节，有三种类型</span></span><br><span class="line">	<span class="comment">// CONNECT X'01'</span></span><br><span class="line">	<span class="keyword">if</span> buf[<span class="number">1</span>] != <span class="number">0x01</span> &#123;</span><br><span class="line">		<span class="comment">// 目前只支持 CONNECT</span></span><br><span class="line">		<span class="keyword">return</span></span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="keyword">var</span> dIP []<span class="keyword">byte</span></span><br><span class="line">	<span class="comment">// aType 代表请求的远程服务器地址类型，值长度1个字节，有三种类型</span></span><br><span class="line">	<span class="keyword">switch</span> buf[<span class="number">3</span>] &#123;</span><br><span class="line">	<span class="keyword">case</span> <span class="number">0x01</span>:</span><br><span class="line">		<span class="comment">//	IP V4 address: X'01'</span></span><br><span class="line">		dIP = buf[<span class="number">4</span> : <span class="number">4</span>+net.IPv4len]</span><br><span class="line">	<span class="keyword">case</span> <span class="number">0x03</span>:</span><br><span class="line">		<span class="comment">//	DOMAINNAME: X'03'</span></span><br><span class="line">		ipAddr, err := net.ResolveIPAddr(<span class="string">"ip"</span>, <span class="keyword">string</span>(buf[<span class="number">5</span>:n<span class="number">-2</span>]))</span><br><span class="line">		<span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">			<span class="keyword">return</span></span><br><span class="line">		&#125;</span><br><span class="line">		dIP = ipAddr.IP</span><br><span class="line">	<span class="keyword">case</span> <span class="number">0x04</span>:</span><br><span class="line">		<span class="comment">//	IP V6 address: X'04'</span></span><br><span class="line">		dIP = buf[<span class="number">4</span> : <span class="number">4</span>+net.IPv6len]</span><br><span class="line">	<span class="keyword">default</span>:</span><br><span class="line">		<span class="keyword">return</span></span><br><span class="line">	&#125;</span><br><span class="line">	dPort := buf[n<span class="number">-2</span>:]</span><br><span class="line">	dstAddr := &amp;net.TCPAddr&#123;</span><br><span class="line">		IP:   dIP,</span><br><span class="line">		Port: <span class="keyword">int</span>(binary.BigEndian.Uint16(dPort)),</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="comment">// 连接真正的远程服务</span></span><br><span class="line">	dstServer, err := net.DialTCP(<span class="string">"tcp"</span>, <span class="literal">nil</span>, dstAddr)</span><br><span class="line">	<span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">		<span class="keyword">return</span></span><br><span class="line">	&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">		<span class="keyword">defer</span> dstServer.Close()</span><br><span class="line">		<span class="comment">// Conn被关闭时直接清除所有数据 不管没有发送的数据</span></span><br><span class="line">		dstServer.SetLinger(<span class="number">0</span>)</span><br><span class="line"></span><br><span class="line">		<span class="comment">// 响应客户端连接成功</span></span><br><span class="line">		<span class="comment">/**</span></span><br><span class="line"><span class="comment">		          +----+-----+-------+------+----------+----------+</span></span><br><span class="line"><span class="comment">		          |VER | REP |  RSV  | ATYP | BND.ADDR | BND.PORT |</span></span><br><span class="line"><span class="comment">		          +----+-----+-------+------+----------+----------+</span></span><br><span class="line"><span class="comment">		          | 1  |  1  | X'00' |  1   | Variable |    2     |</span></span><br><span class="line"><span class="comment">		          +----+-----+-------+------+----------+----------+</span></span><br><span class="line"><span class="comment">		*/</span></span><br><span class="line">		<span class="comment">// 响应客户端连接成功</span></span><br><span class="line">		lsServer.EncodeWrite(localConn, []<span class="keyword">byte</span>&#123;<span class="number">0x05</span>, <span class="number">0x00</span>, <span class="number">0x00</span>, <span class="number">0x01</span>, <span class="number">0x00</span>, <span class="number">0x00</span>, <span class="number">0x00</span>, <span class="number">0x00</span>, <span class="number">0x00</span>, <span class="number">0x00</span>&#125;)</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="comment">// 进行转发</span></span><br><span class="line">	<span class="comment">// 从 localUser 读取数据发送到 dstServer</span></span><br><span class="line">	<span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">		err := lsServer.DecodeCopy(dstServer, localConn)</span><br><span class="line">		<span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">			<span class="comment">// 在 copy 的过程中可能会存在网络超时等 error 被 return，只要有一个发生了错误就退出本次工作</span></span><br><span class="line">			localConn.Close()</span><br><span class="line">			dstServer.Close()</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;()</span><br><span class="line">	<span class="comment">// 从 dstServer 读取数据发送到 localUser，这里因为处在翻墙阶段出现网络错误的概率更大</span></span><br><span class="line">	lsServer.EncodeCopy(localConn, dstServer)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<hr>
<p>以上就是实现一个轻量级 Shadowsocks 的核心代码。其它一些零碎的代码，例如启动入口、配置读写等，可以去 <a href="https://github.com/gwuhaolin/lightsocks" target="_blank" rel="noopener">lightsocks</a> 项目中阅读完整代码。</p>
<p><a href="http://wuhaolin.cn/2017/11/03/%E4%BD%A0%E4%B9%9F%E8%83%BD%E5%86%99%E4%B8%AA%20Shadowsocks/">阅读原文</a></p>


                <hr>

                 <strong><a href="https://github.com/gwuhaolin/blog/issues/12" target="_blank" rel="noopener">在Github上参与本文讨论</a></strong>

                <ul class="pager">
                    
                        <li class="previous">
                            <a href="/2017/12/27/Parcel Vs Webpack/" data-toggle="tooltip" data-placement="top" title="Parcel Vs Webpack">&larr; Previous Post</a>
                        </li>
                    
                    
                        <li class="next">
                            <a href="/2017/11/02/调试利器-SSH隧道/" data-toggle="tooltip" data-placement="top" title="调试利器-SSH隧道">Next Post &rarr;</a>
                        </li>
                    
                </ul>
            </div>
    <!-- Side Catalog Container -->
        

    <!-- Sidebar Container -->

            <div class="
                col-lg-8 col-lg-offset-2
                col-md-10 col-md-offset-1
                sidebar-container">

                <!-- Featured Tags -->
                
                <section>
                    <!-- no hr -->
                    <h5><a href="/tags/">FEATURED TAGS</a></h5>
                    <div class="tags">
                       
                          <a class="tag" href="/tags/#网络" title="网络">网络</a>
                        
                    </div>
                </section>
                

                <!-- Friends Blog -->
                
            </div>

        </div>
    </div>
</article>


<!-- async load function -->
<script>
    function async(u, c) {
      var d = document, t = 'script',
          o = d.createElement(t),
          s = d.getElementsByTagName(t)[0];
      o.src = u;
      if (c) { o.addEventListener('load', function (e) { c(null, e); }, false); }
      s.parentNode.insertBefore(o, s);
    }
</script>
<!-- anchor-js, Doc:http://bryanbraun.github.io/anchorjs/ -->
<script>
    async("https://cdn.bootcss.com/anchor-js/1.1.1/anchor.min.js",function(){
        anchors.options = {
          visible: 'always',
          placement: 'right',
          icon: '#'
        };
        anchors.add().remove('.intro-header h1').remove('.subheading').remove('.sidebar-container h5');
    })
</script>
<style>
    /* place left on bigger screen */
    @media all and (min-width: 800px) {
        .anchorjs-link{
            position: absolute;
            left: -0.75em;
            font-size: 1.1em;
            margin-top : -0.1em;
        }
    }
</style>



<!-- Footer -->
<!-- Footer -->
<footer>
    <div class="container">
        <div class="row">
            <div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
                <ul class="list-inline text-center">
                    
                    
                    
                    <li>
                        <a target="_blank" href="https://www.zhihu.com/people/wu-hao-lin-67-15">
                            <span class="fa-stack fa-lg">
                                <i class="fa fa-circle fa-stack-2x"></i>
                                <i class="fa  fa-stack-1x fa-inverse">知</i>
                            </span>
                        </a>
                    </li>
                    

                    

                    

                    
                    <li>
                        <a target="_blank" href="https://github.com/gwuhaolin">
                            <span class="fa-stack fa-lg">
                                <i class="fa fa-circle fa-stack-2x"></i>
                                <i class="fa fa-github fa-stack-1x fa-inverse"></i>
                            </span>
                        </a>
                    </li>
                    

                    

                </ul>
                <p class="copyright text-muted">
                    Copyright &copy; 浩麟的博客 2019
                    <br>
                    Ported by <a href="https://github.com/gwuhaolin" target="_blank" rel="noopener">gwuhaolin</a> |
                    订阅本站<iframe
                            style="margin-left: 2px; margin-bottom:-5px;"
                            frameborder="0" scrolling="0" width="91px" height="20px"
                            src="https://ghbtns.com/github-btn.html?user=gwuhaolin&repo=blog&type=star&count=true">
                    </iframe>
                </p>
            </div>
        </div>
    </div>
</footer>

<!-- jQuery -->

<script src="/js/jquery.min.js"></script>


<!-- Bootstrap Core JavaScript -->

<script src="/js/bootstrap.min.js"></script>


<!-- Custom Theme JavaScript -->

<script src="/js/hux-blog.min.js"></script>



<!-- async load function -->
<script>
    function async(u, c) {
        var d = document, t = 'script',
            o = d.createElement(t),
            s = d.getElementsByTagName(t)[0];
        o.src = u;
        if (c) {
            o.addEventListener('load', function (e) {
                c(null, e);
            }, false);
        }
        s.parentNode.insertBefore(o, s);
    }
</script>


<!-- jquery.tagcloud.js -->
<script>
    // only load tagcloud.js in tag.html
    if ($('#tag_cloud').length !== 0) {
        async("https://wuhaolin.cn/js/jquery.tagcloud.js", function () {
            $.fn.tagcloud.defaults = {
                //size: {start: 1, end: 1, unit: 'em'},
                color: {start: '#bbbbee', end: '#0085a1'},
            };
            $('#tag_cloud a').tagcloud();
        })
    }
</script>

<!--fastClick.js -->
<script>
    async("https://cdn.bootcss.com/fastclick/1.0.6/fastclick.min.js", function () {
        var $nav = document.querySelector("nav");
        if ($nav) FastClick.attach($nav);
    })
</script>


<!-- Google Analytics -->


<script>
    // dynamic User by Hux
    var _gaId = 'UA-54964850-1';
    var _gaDomain = 'wuhaolin.cn';

    // Originial
    (function (i, s, o, g, r, a, m) {
        i['GoogleAnalyticsObject'] = r;
        i[r] = i[r] || function () {
            (i[r].q = i[r].q || []).push(arguments)
        }, i[r].l = 1 * new Date();
        a = s.createElement(o),
            m = s.getElementsByTagName(o)[0];
        a.async = 1;
        a.src = g;
        m.parentNode.insertBefore(a, m)
    })(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');

    ga('create', _gaId, _gaDomain);
    ga('send', 'pageview');
</script>




<!-- Side Catalog -->


<!--Google Auto AD-->
<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
<script>
    (adsbygoogle = window.adsbygoogle || []).push({
        google_ad_client: "ca-pub-9697944574373240",
        enable_page_level_ads: true
    });
</script>



<!-- Image to hack wechat -->
<img src="https://wuhaolin.cn /img/icon_wechat.png" width="0" height="0"/>
<!-- Migrate from head to bottom, no longer block render and still work -->
</body>

</html>
